"""
连接设备
"""
import json

import IPy
from flask import current_app
from flask_restx import abort, reqparse
from werkzeug.exceptions import HTTPException

from app.api.v1.webhook.webhook import WebHookManager
from tools.basic_tools import Tools
from app.api.v1.organizer.rangeVm import VmBase
from app.models import rangeModel, switchModel, VmType
from tools import db, ops
from collections import OrderedDict
from app.common.transaction_manager import create_transaction


class ConnectDevice(VmBase):
    
    @property
    def load_cloud_object(self):
        return WebHookManager()
    
    def sw_connect_vm(self, range_uuid, device1, device2, ip):
        vm = rangeModel.RangeVm.query.filter_by(vm_uuid=device1["id"]).first()
        current_app.logger.info(device1)
        if not vm:
            abort(400, message="该虚拟机未创建")
        sw = switchModel.Switchs.query.filter_by(
            range_uuid=range_uuid, subnet_id=device2["id"]
        ).first()
        if not sw:
            abort(400, message="该交换机未创建")
        if sw.cidr == "10.0.0.0/24" and vm.type != "HIDS":
            abort(400, message="该交换机仅用路由器连通")

        if not vm.subnet_id:
            return self._extracted_from_sw_connect_vm(sw, device1, range_uuid, vm, ip)
        else:
            abort(400, message="已存在相连的交换机")

    def _extracted_from_sw_connect_vm(self, sw, device1, range_uuid, vm, ip):
        subnet_id = sw.subnet_id
        rangeModel.RangeVm.query.filter_by(vm_uuid=device1["id"]).update(
            {"subnet_id": subnet_id}
        )
        # 创建port连接
        port_id = Tools.get_uuid4_no_line()
        # 开机状态添加虚拟网卡需要关机操作
        if vm.op_vm_uuid:
            info = self.load_cloud_object.get_vm_details(vm.op_vm_uuid)
            if not info or info[0]["state"] == "Running":
                abort(400, message="开机状态下虚拟机添加端口需要先关机")
            action_params = OrderedDict()
            action_params["create_vm_nic"] = {"network_uid": subnet_id, "resource_uuid": port_id, "ip": ip}
            action_params["attach_vm_nic_vm"] = {"vm_uuid": vm.op_vm_uuid, "state": 1}
            action_params["update_vm_ip_database"] = {"port_id": port_id, "device_type": "vm", "op_vm_uuid": vm.op_vm_uuid}
            create_transaction(action_params, vm.range_uuid)
        try:
            port = switchModel.Ports(
                    port_id=port_id,
                    range_uuid=range_uuid,
                    ip=ip,
                    network_id=sw.network_id,
                    subnet_id=subnet_id,
                    device_id=vm.vm_uuid,
                    device_owner=vm.type
                )
            switchModel.db.session.add(port)
            switchModel.db.session.commit()
        except Exception:
            switchModel.db.session.rollback()
            abort(400, message="交换机与虚拟机关联失败")
        
        return {"ip": ip, "port_id": port_id} if ip else {"port_id": port_id}

    def sw_connect_router(self, range_uuid, router, sw, ip):

        router_bd = switchModel.Routes.query.filter_by(
            range_uuid=range_uuid, op_route=router["id"]
        ).first()
        switch = switchModel.Switchs.query.filter_by(
            range_uuid=range_uuid, subnet_id=sw["id"]
        ).first()
        if not router_bd:
            abort(400, message="该路由器未创建")
        if not switch:
            abort(400, message="该交换机未创建")

        port = switchModel.Ports.query.filter_by(
            range_uuid=range_uuid, subnet_id=sw["id"], device_owner="router_gw"
        ).first()
        ip_list = list(IPy.IP(switch.cidr))
        if port:
            abort(400, message="交换机与路由器已经有关联")
        else:
            gw_ip = str(ip_list[1])
            self._extracted_from_sw_connect_router(router, sw, range_uuid, switch, gw_ip)
        # 加载
        if not router_bd.binding_switchs:
            switchModel.Routes.query.filter_by(
                range_uuid=range_uuid, op_route=router["id"]
            ).update({"binding_switchs": switch.id})
        else:
            binding_switchs = f"{router_bd.binding_switchs},{str(switch.id)}"
            switchModel.Routes.query.filter_by(
                range_uuid=range_uuid, op_route=router["id"]
            ).update({"binding_switchs": binding_switchs})

    def _extracted_from_sw_connect_router(self, router, sw, range_uuid, switch, gw_ip):
        # vpc网络绑定路由
        try:
            port = switchModel.Ports(
                port_id=Tools.get_uuid4_no_line(),
                range_uuid=range_uuid,
                ip=gw_ip,
                network_id=switch.network_id,
                subnet_id=switch.subnet_id,
                device_id=router["id"],
                device_owner="router_gw",
            )
            db.session.add(port)
            db.session.flush()
            # 加载前检查是否有VRouterRoute
            action_params = OrderedDict()
            if not self._check_switch_to_attach_detach(switch.subnet_id):
                action_params["attach_network_service_to_l3Network"] = {
                    "l3_network_uuid": switch.subnet_id,
                    "network_service": {current_app.config["ZSTACK_CLOUD"]["network_route_eip_service"]: ["VRouterRoute"]}
                }
            action_params["attach_l3network_route"] = {
                "route_uuid": port.device_id, "l3_network_uuid": port.subnet_id, "state": 1
            }
            action_params["update_vm_ip_database"] = {
                 "port_id": port.port_id,
                 "device_type": "router",
                 "l3_network_uuid": port.subnet_id
            }
            create_transaction(action_params, range_uuid)
        except Exception:
            db.session.rollback()
            abort(400, message="绑定失败")
        return
    
    def _check_switch_to_attach_detach(self, subnet_id):
        """检测交换机"""
        switch_info = self.load_cloud_object.get_l3_network(subnet_id)
        for switch in  switch_info:
            for service in  switch["networkServices"]:
                if service["networkServiceType"] == "VRouterRoute":
                    return  True
        return False

    def sw_connect_firewall(self, range_uuid, fw, sw, ip):
        firewall_vm = rangeModel.RangeVm.query.filter_by(
            vm_uuid=fw["id"]
        ).first()
        switch = switchModel.Switchs.query.filter_by(
            range_uuid=range_uuid, subnet_id=sw["id"]
        ).first()

        if not firewall_vm:
            abort(400, message="防火墙未创建!")
        if firewall_vm.type != 'firewall':
            abort(400, message="虚拟机非防火墙类型!")
        if not switch:
            abort(400, message="该交换机未创建!")
        # change: 22.10.20 10段交换机禁止连接防火墙
        if switch.cidr == '10.0.0.0/24':
            abort(400, message="该网段交换机限制与防火墙连接!")
        # 检查防火墙状态
        if firewall_vm.op_vm_uuid:
            server = self.conn.compute.find_server(firewall_vm.op_vm_uuid)
            if server.status != 'SHUTOFF':
                abort(400, message="防火墙更新接线时必须设置为关机状态!")
        # 限制防火墙上交换机连接的数量
        connect_count = switchModel.Ports.query.filter_by(
            range_uuid=range_uuid, device_id=firewall_vm.vm_uuid, device_owner='firewall_gw'
        ).count()
        if connect_count >= 5:
            abort(400, message="防火墙连接设备数量达到上限!")

        subnet = self.conn.get_subnet(switch.subnet_id)
        filters = {
            'network_id': switch.network_id,
            'subnet_id': switch.subnet_id
        }
        ports = self.conn.network.ports(**filters)
        gateway_port = next(
            (
                port
                for port in ports
                if port.fixed_ips[0]['ip_address'] == subnet.gateway_ip
            ),
            None,
        )
        # 网关ip未创建时创建
        if not gateway_port:
            gateway_port = self.conn.create_port(
                switch.network_id,
                fixed_ips=[
                    {"ip_address": subnet.gateway_ip, "subnet_id": switch.subnet_id}
                ],
                port_security_enabled=False
            )
        elif gateway_port.device_id:
            abort(400, message="该交换机网关端口被占用!")
        if firewall_vm.op_vm_uuid:
            # 绑定端口到虚拟机实例
            self.conn.compute.create_server_interface(
                firewall_vm.op_vm_uuid, port_id=gateway_port.id
            )
        # 保存相关信息至数据库中
        port_obj = switchModel.Ports.query.filter_by(
            range_uuid=range_uuid, subnet_id=subnet.id, ip=subnet.gateway_ip
        ).first()
        if not port_obj:
            port_obj = switchModel.Ports(
                port_id=gateway_port.id,
                range_uuid=range_uuid,
                ip=subnet.gateway_ip,
                network_id=switch.network_id,
                subnet_id=switch.subnet_id,
                device_id=firewall_vm.vm_uuid,
                device_owner="firewall_gw"
            )
            db.session.add(port_obj)
        else:
            port_obj.network_id = switch.network_id
            port_obj.device_id = firewall_vm.vm_uuid
            port_obj.ip = subnet.gateway_ip
            port_obj.device_owner = "firewall_gw"

        # TODO: 检查字段在其他接口的是否特殊作用
        # rangeModel.RangeVm.query.filter_by(vm_uuid=fw["id"]).update(
        #     {"subnet_id": switch.subnet_id}
        # )
        self.conn.update_port(
            gateway_port.id,
            port_security_enabled=False
        )

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", type=str, help="UUID不能为空", required=True)
        parser.add_argument("device1", type=dict)
        parser.add_argument("device2", type=dict)
        parser.add_argument("ip", type=str)
        parser.add_argument("type", type=str)

        args = parser.parse_args()

        device1 = args["device1"]
        device2 = args["device2"]
        
        ip = args["ip"]

        type1 = device1["type"]
        type2 = device2["type"]

        range_config = current_app.config
        vm_type_list = list(range_config["RANGE_CONF"]["RangeVmType"].values())
        if all([
            type1 in vm_type_list,
            type1 not in ['vpn', 'firewall']
        ]):
            type1 = "vm"
        elif all([
            type2 in vm_type_list,
            type2 not in ['vpn', 'firewall']
        ]):
            type2 = "vm"

        con_type_to_connect_funcs = {
            ('switch', 'vm'): self.sw_connect_vm,
            ('switch', VmType.container): self.sw_connect_vm,
            ('switch', 'router'): self.sw_connect_router,
            ('switch', 'vpn'): self.sw_connect_vm,
            ('switch', 'firewall'): self.sw_connect_firewall,
        }

        connect_func = con_type_to_connect_funcs.get(
            (type1, type2),
            con_type_to_connect_funcs.get((type2, type1), None)
        )

        if not connect_func:
            abort(400, message="不支持的接线方式")

        # 调换 switch 顺序 -> 最后
        if type1 == 'switch':
            device1, device2 = device2, device1

        try:
            params = (args["range_uuid"], device1, device2, ip)
            res = connect_func(*params)
            db.session.commit()
        except HTTPException as e:
            db.session.rollback()
            raise e from e
        except Exception as e:
            db.session.rollback()
            current_app.logger.error("连线失败", exc_info=e)
            abort(400, message="连线失败")
        else:
            response = {"code": 200, "info": "连接成功"}
            if res:
                response["port_info"] = res
            return response

    def delete(self):
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", type=str, help="UUID不能为空", required=True)
        parser.add_argument("device1", type=str)
        parser.add_argument("device2", type=str)
        parser.add_argument("type", type=str)
        args = parser.parse_args()

        device1 = json.loads(args["device1"].replace("'", '"'))
        device2 = json.loads(args["device2"].replace("'", '"'))

        type1 = device1["type"]
        type2 = device2["type"]

        range_config = current_app.config
        vm_type_list = list(range_config["RANGE_CONF"]["RangeVmType"].values())
        if all([
            type1 in vm_type_list,
            type1 not in ['firewall']
        ]):
            type1 = "vm"
        if all([
            type2 in vm_type_list,
            type2 not in ['firewall']
        ]):
            type2 = "vm"

        del_con_type_to_connect_funcs = {
            ('switch', 'vm'): self.sw_vm_delete,
            ('switch', 'container'): self.sw_vm_delete,
            ('switch', 'router'): self.sw_router_delete,
            ('switch', 'firewall'): self.sw_firewall_delete,
        }

        del_connect_func = next(
            (
                _connect_func
                for con_type, _connect_func in del_con_type_to_connect_funcs.items()
                if {type1, type2} == set(con_type)
            ),
            None,
        )
        if not del_connect_func:
            abort(400, message="不支持的接线方式")

        # 调换 switch 顺序 -> 最后
        if type1 == 'switch':
            device1, device2 = device2, device1
        try:
            params = (args["range_uuid"], device1, device2)
            del_connect_func(*params)
            return {"code": 200, "info": "删除成功"}
        except HTTPException as e:
            db.session.rollback()
            raise e from e
        except Exception as e:
            db.session.rollback()
            current_app.logger.error("删除连线失败", exc_info=e)
            abort(400, message="删除失败")

    @staticmethod
    def sw_firewall_delete(range_uuid, fw, sw):
        from tasks.tasks import delete_port

        firewall_vm = rangeModel.RangeVm.query.filter_by(
            vm_uuid=fw["id"]
        ).first()
        switch = switchModel.Switchs.query.filter_by(
            subnet_id=sw["id"]
        ).first()
        port_obj = switchModel.Ports.query.filter_by(
            range_uuid=range_uuid,
            subnet_id=switch.subnet_id,
            device_id=fw["id"],
        ).first()
        # 分别解绑端口
        if port_obj:
            delete_port.apply_async(args=(
                port_obj.port_id, firewall_vm.op_vm_uuid, 0
            ))

        rangeModel.RangeVm.query.filter_by(vm_uuid=fw["id"]).update(
            {"subnet_id": None}
        )
        db.session.commit()
        return

    def sw_vm_delete(self, range_uuid, device1, device2):
        vm = rangeModel.RangeVm.query.filter_by(vm_uuid=device1["id"]).first()
        sw = switchModel.Switchs.query.filter_by(subnet_id=device2["id"]).first()
        vm_port = switchModel.Ports.query.filter_by(
                range_uuid=range_uuid, device_id=device1["id"], subnet_id=sw.subnet_id
            ).first()
        action_params = OrderedDict()
        if vm.op_vm_uuid:
            action_params["detach_l3_network_vm"] = {"vm_network_id": vm_port.port_id}
        action_params["del_connect_vm_database"] = {"port_id": vm_port.port_id, "state": 1}
        create_transaction(action_params, range_uuid)

    def sw_router_delete(self, range_uuid, router, switch):
        router_db = switchModel.Routes.query.filter_by(
            range_uuid=range_uuid, op_route=router["id"]
        ).first()
        switch_db = switchModel.Switchs.query.filter_by(
            range_uuid=range_uuid, subnet_id=switch["id"]
        ).first()
        if not router_db:
            abort(400, message="该路由器未创建")
        if not switch_db:
            abort(400, message="该交换机未创建")
        
        port = switchModel.Ports.query.filter_by(
                range_uuid=range_uuid, subnet_id=switch["id"], device_owner="router_gw"
            ).first()
        action_params = OrderedDict()
        action_params["del_vm_nic"] = {"network_nic_uuid": port.port_id}
        action_params["del_connect_router_database"] = {"port_id": port.port_id, "state": 1}
        create_transaction(action_params, range_uuid)
